package com.beone.admin.config.security;

import com.beone.admin.security.filter.MyAccessDecisionManager;
import com.beone.admin.security.filter.UrlFilterInvocationSecurityMetadataSource;
import com.beone.admin.security.filter.UrlObjectPostProcessor;
import com.beone.admin.security.filter.ValidateCodeUsernamePasswordAuthenticationFilter;
import com.beone.admin.security.handler.AccessDeniedSupportAjaxHandler;
import com.beone.admin.security.handler.RequestTypeAuthenticationEntryPoint;
import com.beone.admin.security.handler.SimpleLoginSuccessHandler;
import com.beone.admin.service.SysLogService;
import com.beone.admin.service.UrlSecurityUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;

import java.util.ArrayList;
import java.util.List;

/**
 * @title
 * @Author 覃球球
 * @Version 1.0 on 2017/12/20.
 * @Copyright 长笛龙吟
 */
@Configuration
@EnableWebSecurity
//覆盖默认的spring security提供的配置
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SessionRegistry sessionRegistry;

    @Autowired
    private UrlSecurityUserDetailsService userDetailsService;

    @Autowired
    private SysLogService sysLogService;

    public void configure(WebSecurity web) throws Exception {
        //不进行认证处理，直接允许访问
        web.ignoring().antMatchers( "/druid/**","/login","/getCode","/error/**", "/static/**"
                ,"/fonts/**","/favicon.ico","/", "/logout", "/admin/**", "/auto/**");

        //webjars resources
        web.ignoring().antMatchers("/v2/api-docs","/swagger-ui.html","/webjars-ui.html"
                ,"/webjars/**","/swagger-resources/**");
    }

    /**
     * Csrf 请求
     * @return
    @Bean
    public CsrfFilter csrfFilter(){
        return new CsrfFilter(new HttpSessionCsrfTokenRepository());
    }
     */

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /* iframe嵌入网页，然后用到springsecurity就被拦截了 浏览器报错  x-frame-options deny
         原因是因为springSecurty使用X-Frame-Options防止网页被Frame */
        http.headers().frameOptions().disable();
        http.authorizeRequests()
                .anyRequest().authenticated() //排除以上url，都需要进行认证访问
                .withObjectPostProcessor(getObjectPostProcessor())
                .and()
                .csrf().disable() //关闭 Spring Security Csrf 功能
            .formLogin()
                .loginPage("/login") //登录页
                //.usernameParameter("username") //用户参数名
                //.passwordParameter("password") //密码参数名
                //.loginProcessingUrl("/loginValidator") //自定义登录验证ULR
                .defaultSuccessUrl("/system/index")  //登录成功后的跳转页面
                //.failureHandler(authenticationFailureHandler()) //登录验证错误页面
                .and()
            .rememberMe()
                .and()
            .logout()
//                .logoutUrl("/logoutExit").permitAll() //退出Url
//                .invalidateHttpSession(true) //销毁session
                .logoutUrl("/logout?logout")//退出Url  default url  /logout
                .clearAuthentication(true) //清空Security 认证信息
                .and()
            .sessionManagement()
                .maximumSessions(1)
                .sessionRegistry(sessionRegistry)
                .and().and()
            .exceptionHandling()
                .authenticationEntryPoint(new RequestTypeAuthenticationEntryPoint("/login")) //检查认证资源不没有认证时的跳转至登录Url
//                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
                .accessDeniedHandler(getAccessDeniedHandler()) //没有权限时任务处理
                .and()

//            .httpBasic()
            ;
        //http.removeConfigurer(CsrfConfigurer.class); //删除Spring Security Csrf 自带功能; 忽略静态资源不拦截
        //http.addFilterBefore(csrfFilter(), CsrfFilter.class); //支持忽略的静态资源url

        http.addFilterAt(custAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        /*http.addFilterAfter(new RememberMeAuthenticationFilter(this.authenticationManager()
                        , new TokenBasedRememberMeServices("BEONE", userDetailsService))
                , ValidateCodeUsernamePasswordAuthenticationFilter.class);*/
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
        /**
           hideUserNotFoundExceptions属性，默认是true。
         这样的话即便我们抛出了UsernameNotFoundException它也会转为BadCredentialsException，
         所以我们需要将hideUserNotFoundExceptions属性的值设为false
         */
        daoProvider.setHideUserNotFoundExceptions(false);
        daoProvider.setUserDetailsService(userDetailsService);
        daoProvider.setPasswordEncoder(new Md5PasswordEncoder());
        auth.authenticationProvider(daoProvider);
    }


    /**
     * Url Security 增加自定义的投票器
     * @return
     * @throws Exception
     */
    @Bean
    public MyAccessDecisionManager accessDecisionManager(){
        List<AccessDecisionVoter<? extends Object>> voteList = new ArrayList<AccessDecisionVoter<? extends Object>>();
        voteList.add(webExpressionVoter());
        voteList.add(new AuthenticatedVoter());
        voteList.add(new RoleVoter());
        return new MyAccessDecisionManager(voteList);
    }

    /*
         * 表达式控制器
         */
    @Bean(name = "expressionHandler")
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler webSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
        return webSecurityExpressionHandler;
    }


    /*
     * 表达式投票器
     */
    @Bean(name = "expressionVoter")
    public WebExpressionVoter webExpressionVoter() {
        WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
        webExpressionVoter.setExpressionHandler(webSecurityExpressionHandler());
        return webExpressionVoter;
    }

    @Bean(name = "securityMetadataSource")
    public UrlFilterInvocationSecurityMetadataSource securityMetadateSource(){
        return new UrlFilterInvocationSecurityMetadataSource();
    }

    @Bean
    public UrlObjectPostProcessor getObjectPostProcessor() throws Exception{
        UrlObjectPostProcessor postProcessor = new UrlObjectPostProcessor();
        postProcessor.setSecurityMetadataSource(securityMetadateSource());
        postProcessor.setAccessDecisionManager(accessDecisionManager());
        postProcessor.setAuthenticationManager(authenticationManagerBean());
        return postProcessor;
    }

    /**
     * https://www.jianshu.com/p/9b798f0edef2  RememberMe Spring Boot Security 参考
     * @return
     */
    @Bean
    public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
        TokenBasedRememberMeServices tbrms = new TokenBasedRememberMeServices("BEONE", userDetailsService);
        // 设置cookie过期时间为2天
        tbrms.setTokenValiditySeconds(60 * 60 * 24 * 30);  //cookie token 有效期一个月
        tbrms.setParameter("rememberMe");
        return tbrms;
    }

    /**
     * 图形验证码 Security 扩展
     * @return
     */
    public ValidateCodeUsernamePasswordAuthenticationFilter custAuthenticationFilter()
        throws Exception{
        ValidateCodeUsernamePasswordAuthenticationFilter filter = new ValidateCodeUsernamePasswordAuthenticationFilter();
        filter.setFilterProcessesUrl("/loginValidator");
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationFailureHandler(authenticationFailureHandler());
        filter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
        filter.setRememberMeServices(tokenBasedRememberMeServices());
        return filter;
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler(){
        return new SimpleUrlAuthenticationFailureHandler("/login?error");
    }

    @Bean
    public SimpleLoginSuccessHandler authenticationSuccessHandler(){
        SimpleLoginSuccessHandler handler = new SimpleLoginSuccessHandler();
        handler.setDefaultTargetUrl("/system/index");
        handler.setSysLogService(sysLogService); //注入日志服务类
        return handler;
    }

   @Bean
    public AccessDeniedSupportAjaxHandler getAccessDeniedHandler(){
        AccessDeniedSupportAjaxHandler handler = new AccessDeniedSupportAjaxHandler();
        handler.setErrorPage("/error/403");
        return handler;
    }

    @Bean
    public SessionRegistry getSessionRegistry(){
        return new SessionRegistryImpl();
    }
}
